#include "scene.h"
#include "objects.h"
#include "texture.h"
#include "pugixml/src/pugixml.hpp"


using rtMath::vec3;


Scene::Scene()
	: m_Camera(NULL)
{
}
Scene::~Scene()
{
	SafeDelete(m_Camera);
}


bool Scene::LoadFromFile(const rtString& fileName)
{
	rtString fDir, fName;
	SplitPathName(fileName, fDir, fName);
	m_SceneLoadDir = fDir;

	pugi::xml_document doc;
	pugi::xml_parse_result result = doc.load_file(fileName.c_str());
	if (!result)
		return false;

	pugi::xml_node sceneNode = doc.child("cuTracer").child("scene");
	if (sceneNode.empty())
		return false;

	pugi::xml_node cameraNode = sceneNode.child("camera");
	if (!cameraNode.empty())
	{
		vec3 camPosition = this->ReadVectorFromXml(cameraNode, "position");
		vec3 camDirection = this->ReadVectorFromXml(cameraNode, "dir");
		vec3 camUpVector = this->ReadVectorFromXml(cameraNode, "up");
		float fov = atof(cameraNode.child("fov").child_value());
		float nearClip = cameraNode.child("clip").attribute("near").as_float();
		float farClip = cameraNode.child("clip").attribute("far").as_float();

		m_Camera = new SceneCamera();
		m_Camera->SetPosition(camPosition);
		m_Camera->SetDirection(camDirection);
		m_Camera->SetUpVector(camUpVector);
		m_Camera->SetFOV(fov);
		m_Camera->SetNearClip(nearClip);
		m_Camera->SetFarClip(farClip);
	}

	pugi::xml_node materialsNode = sceneNode.child("materials");
	if (!materialsNode.empty())
	{
		for (pugi::xml_node::iterator it = materialsNode.begin(); it != materialsNode.end(); ++it)
			this->ReadMaterial(*it);
	}

	pugi::xml_node objectsNode = sceneNode.child("objects");
	if (!objectsNode.empty())
	{
		for (pugi::xml_node::iterator it = objectsNode.begin(); it != objectsNode.end(); ++it)
			this->ReadObject(*it);
	}

	if (!m_Camera)
		m_Camera = new SceneCamera();

	return true;
}

int Scene::GetMaterialsCount(void) const
{
	return static_cast<int>(m_Materials.size());
}

const Material* Scene::GetMaterial(int idx) const
{
	return m_Materials[idx];
}

int Scene::FindMaterial(const rtString& materialName) const
{
	int idx = -1;
	int i = 0;
	for (materialsVec::const_iterator it = m_Materials.begin(); it != m_Materials.end(); ++it, ++i)
		if ((*it)->GetName() == materialName)
		{
			idx = i;
			break;
		}

	return idx;
}

int Scene::GetTexturesCount(void) const
{
	return static_cast<int>(m_Textures.size());
}

const Texture* Scene::GetTexture(int idx) const
{
	return m_Textures[idx];
}

int Scene::FindTexture(const rtString& textureName) const
{
	int idx = -1;
	int i = 0;
	for (texturesVec::const_iterator it = m_Textures.begin(); it != m_Textures.end(); ++it, ++i)
		if ((*it)->GetName() == textureName)
		{
			idx = i;
			break;
		}

	return idx;
}

int Scene::GetShapesCount(void) const
{
	return static_cast<int>(m_Shapes.size());
}

const SceneShape* Scene::GetShape(int idx) const
{
	return m_Shapes[idx];
}

int Scene::GetLightsCount(void) const
{
	return static_cast<int>(m_Lights.size());
}

const SceneLight* Scene::GetLight(int idx) const
{
	return m_Lights[idx];
}

SceneCamera* Scene::GetCamera(void) const
{
	return m_Camera;
}



vec3 Scene::ReadVectorFromXml(pugi::xml_node& node, const rtString& vecName)
{
	vec3 result;
	pugi::xml_node vecNode = node.child(vecName.c_str());
	if (!vecNode.empty())
	{
		result.x = vecNode.attribute("x").as_float();
		result.y = vecNode.attribute("y").as_float();
		result.z = vecNode.attribute("z").as_float();
	}

	return result;
}

bool Scene::ReadMaterial(pugi::xml_node& node)
{
	rtString name = node.attribute("name").value();

	vec3 diffuse;
	diffuse.r = node.child("diffuse").attribute("r").as_float();
	diffuse.g = node.child("diffuse").attribute("g").as_float();
	diffuse.b = node.child("diffuse").attribute("b").as_float();

	vec3 specular;
	specular.r = node.child("specular").attribute("r").as_float();
	specular.g = node.child("specular").attribute("g").as_float();
	specular.b = node.child("specular").attribute("b").as_float();

	float specular_level = atof(node.child("specular_level").child_value());
	float glossiness = atof(node.child("glossiness").child_value());
	float transparency = atof(node.child("transparency").child_value());
	float reflectivity = atof(node.child("reflectivity").child_value());
	float refrIndex = atof(node.child("refr_index").child_value());

	rtString albedo = node.child("albedo").child_value();
	rtString bump = node.child("bump").child_value();

	Material* material = new Material();
	if (material)
	{
		material->SetName(name);
		material->SetDiffuse(diffuse);
		material->SetSpecular(specular);
		material->SetSpecularLevel(specular_level);
		material->SetGlossiness(glossiness);
		material->SetTransparency(transparency);
		material->SetReflectivity(reflectivity);
		material->SetRefractionIndex(refrIndex);

		if (!albedo.empty())
			material->SetAlbedoTexture(this->CreateTexture(albedo));

		if (!bump.empty())
			material->SetBumpTexture(this->CreateTexture(bump));

		m_Materials.push_back(material);
	}

	return material != NULL;
}

bool Scene::ReadObject(pugi::xml_node& node)
{
	rtString objName = node.attribute("name").value();
	rtString objType = node.attribute("type").value();

	vec3 objPosition = this->ReadVectorFromXml(node, "position");

	SceneObject* object = NULL;

	if (objType == "shape")
	{
		SceneShape* shape = this->ReadShape(node);
		if (!shape)
			return false;

		m_Shapes.push_back(shape);
		object = static_cast<SceneObject*>(shape);
	}
	else if (objType == "light")
	{
		SceneLight* light = this->ReadLight(node);
		if (!light)
			return false;

		m_Lights.push_back(light);
		object = static_cast<SceneObject*>(light);
	}
	else
		return false;

	object->SetName(objName);
	object->SetPosition(objPosition);

	return true;
}

SceneShape* Scene::ReadShape(pugi::xml_node& node)
{
	pugi::xml_node shapeNode = node.child("shape");
	rtString shapeType = shapeNode.attribute("type").value();

	SceneShape* shape = NULL;
	if (shapeType == "sphere")
	{
		ShapeSphere* sphere = new ShapeSphere();
		if (sphere)
		{
			float radius = atof(shapeNode.child("radius").child_value());
			sphere->SetRadius(radius);

			shape = static_cast<SceneShape*>(sphere);
		}
	}
	else if (shapeType == "box")
	{
		ShapeBox* box = new ShapeBox();
		if (box)
		{
			vec3 aabbmin = this->ReadVectorFromXml(shapeNode, "aabbmin");
			vec3 aabbmax = this->ReadVectorFromXml(shapeNode, "aabbmax");
			box->SetMinimum(aabbmin);
			box->SetMaximum(aabbmax);

			shape = static_cast<SceneShape*>(box);
		}
	}
	else if (shapeType == "plane")
	{
		ShapePlane* plane = new ShapePlane();
		if (plane)
		{
			vec3 normal = this->ReadVectorFromXml(shapeNode, "normal");
			float distance = atof(shapeNode.child("distance").child_value());
			plane->SetNormal(normal);
			plane->SetDistance(distance);
			plane->Setup();

			shape = static_cast<SceneShape*>(plane);
		}
	}

	pugi::xml_node matNode = shapeNode.child("material");
	if (shape && !matNode.empty())
		shape->SetMaterialID(this->FindMaterial(matNode.child_value()));

	return shape;
}

SceneLight* Scene::ReadLight(pugi::xml_node& node)
{
	pugi::xml_node shapeNode = node.child("light");
	rtString shapeType = shapeNode.attribute("type").value();

	SceneLight* light = new SceneLight();
	if (light)
	{
		pugi::xml_node intensityNode = shapeNode.child("intensity");
		vec3 intensity;
		intensity.x = intensityNode.attribute("r").as_float();
		intensity.y = intensityNode.attribute("g").as_float();
		intensity.z = intensityNode.attribute("b").as_float();

		light->SetIntensity(intensity);
	}

	return light;
}

int Scene::CreateTexture(const rtString& textureName)
{
	int idx = this->FindTexture(textureName);

	if (-1 == idx)
	{
		rtString fullPath = m_SceneLoadDir + textureName;

		Texture* texture = new Texture();
		if (texture->LoadFromFile(fullPath))
		{
			idx = this->GetTexturesCount();
			m_Textures.push_back(texture);
		}
	}

	return idx;
}
